package com.colors.feeds.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.colors.commons.constant.ApiConstant;
import com.colors.commons.constant.RedisKeyConstant;
import com.colors.commons.exception.ParameterException;
import com.colors.commons.model.domain.ResultInfo;
import com.colors.commons.model.pojo.Feeds;
import com.colors.commons.model.vo.FeedsVO;
import com.colors.commons.model.vo.ShortDinerInfo;
import com.colors.commons.model.vo.SignInDinerInfo;
import com.colors.commons.utils.AssertUtil;
import com.colors.feeds.mapper.FeedsMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author colors
 * @date 2021/5/12 17:27
 */
@Service
public class FeedsService {

    @Value("${service.name.server-oauth2}")
    private String oauthServerName;
    @Value("${service.name.server-follow}")
    private String followServerName;
    @Value("${service.name.server-diners}")
    private String dinersServerName;
    @Resource
    private RestTemplate restTemplate;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private FeedsMapper feedsMapper;

    /**
     * 获取feed
     *
     * @param page        页码
     * @param accessToken token
     * @return
     */
    public List<FeedsVO> selectForPage(Integer page, String accessToken) {
        if (page == null) {
            page = 1;
        }
        // 获取登录用户
        AssertUtil.isNotEmpty(accessToken, "请登录");
        SignInDinerInfo signInDinerInfo = loadSignInDinerInfo(accessToken);
        // 根据 用户id 获取所有的 feed id
        String key = RedisKeyConstant.following_feeds.getKey() + signInDinerInfo.getId();
        long start = (page - 1) * ApiConstant.PAGE_SIZE;
        long end = page * ApiConstant.PAGE_SIZE - 1;
        Set<Integer> feedIds = redisTemplate.opsForZSet().reverseRange(key, start, end);
        if (CollectionUtil.isEmpty(feedIds)) {
            return new ArrayList<>();
        }
        // 根据 feed id 获取feed 信息
        List<Feeds> feedsInfo = feedsMapper.findFeedsByIds(feedIds);
        // 根据 返回的 feed 信息拿到发feed的用户id集合
        List<Integer> feedDinerIds = new ArrayList<>();
        List<FeedsVO> feedsVOS = feedsInfo.stream().map(feed -> {
            FeedsVO feedsVO = new FeedsVO();
            BeanUtil.copyProperties(feed, feedsVO);
            // 添加用户 ID
            feedDinerIds.add(feed.getFkDinerId());
            return feedsVO;
        }).collect(Collectors.toList());
        // 根据 id 集合 调用diners服务获取到食客信息
        ResultInfo resultInfo = restTemplate.getForObject(dinersServerName + "findByIds?access_token={accessToken}&ids={dinersIds}",
                ResultInfo.class, accessToken, feedDinerIds);
        if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            throw new ParameterException(resultInfo.getCode(), resultInfo.getMessage());
        }
        List<LinkedHashMap> dinerInfoMaps = (List<LinkedHashMap>) resultInfo.getData();
        // 构建一个map，key是食客id，value是食客信息
        Map<Integer, ShortDinerInfo> dinersInfo = dinerInfoMaps.stream()
                .collect(Collectors.toMap(
                        diner -> (Integer) diner.get("id"),
                        diner -> BeanUtil.fillBeanWithMap(diner, new ShortDinerInfo(), true)
                ));
        // 遍历 feed信息 集合填入食客信息，最终返回一个装有feed信息和对应发布feed的食客信息
        feedsVOS.forEach(feedsVO -> {
            feedsVO.setDinerInfo(dinersInfo.get(feedsVO.getFkDinerId()));
        });
        return feedsVOS;
    }

    /**
     * 变更 Feed
     *
     * @param followingDinerId 关注的好友 ID
     * @param accessToken      登录用户token
     * @param type             1 关注 0 取关
     */
    @Transactional(rollbackFor = Exception.class)
    public void addFollowingFeed(Integer followingDinerId, String accessToken, int type) {
        // 请选择关注的好友
        AssertUtil.isTrue(followingDinerId == null || followingDinerId < 1,
                "请选择关注的好友");
        // 获取登录用户信息
        SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);
        // 获取关注/取关的食客的所有 Feed
        List<Feeds> feedsList = feedsMapper.findByDinerId(followingDinerId);
        String key = RedisKeyConstant.following_feeds.getKey() + dinerInfo.getId();
        if (type == 0) {
            // 取关
            List<Integer> feedIds = feedsList.stream()
                    .map(feed -> feed.getId())
                    .collect(Collectors.toList());
            redisTemplate.opsForZSet().remove(key, feedIds.toArray(new Integer[]{}));
        } else {
            // 关注
            Set<ZSetOperations.TypedTuple> typedTuples =
                    feedsList.stream()
                            .map(feed -> new DefaultTypedTuple<>(feed.getId(), (double) feed.getUpdateDate().getTime()))
                            .collect(Collectors.toSet());
            redisTemplate.opsForZSet().add(key, typedTuples);
        }
    }

    /**
     * 删除 feed
     *
     * @param id
     * @param accessToken
     */
    public void delete(Integer id, String accessToken) {
        AssertUtil.isTrue(id == null || id < 1, "请选择需要删除的feed");
        SignInDinerInfo signInDinerInfo = loadSignInDinerInfo(accessToken);
        Feeds feeds = feedsMapper.findById(id);
        AssertUtil.isTrue(feeds == null, "feed已删除，请勿重复删除");
        AssertUtil.isTrue(!signInDinerInfo.getId().equals(feeds.getFkDinerId()), "只能删除自己的feed");
        int count = feedsMapper.delete(id);
        if (count == 0) {
            return;
        }
        List<Integer> followers = findFollowers(signInDinerInfo.getId());
        followers.forEach(follower -> {
            String key = RedisKeyConstant.following_feeds.getKey() + follower;
            redisTemplate.opsForZSet().remove(key, feeds.getId());
        });

    }

    @Transactional(rollbackFor = Exception.class)
    public void create(Feeds feeds, String accessToken) {
        // 检验 feed 内容不能为空，不能过长
        AssertUtil.isNotNull(feeds.getContent(), "请输入内容");
        AssertUtil.isTrue(feeds.getContent().length() > 250, "内容过长");
        // 获取登录用户信息
        AssertUtil.isNotEmpty(accessToken, "请登录");
        SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);
        // feed 关联用户信息
        feeds.setFkDinerId(dinerInfo.getId());
        // 添加feed
        int count = feedsMapper.save(feeds);
        AssertUtil.isTrue(count == 0, "添加失败");
        // 推送到粉丝的列表中 -- 后续这里采用异步消息队列解决性能问题
        // 获取粉丝
        List<Integer> followers = findFollowers(dinerInfo.getId());
        // 推送feed
        long now = System.currentTimeMillis();
        followers.forEach(follower -> {
            String key = RedisKeyConstant.following_feeds.getKey() + follower;
            redisTemplate.opsForZSet().add(key, feeds.getId(), now);
        });
    }

    /**
     * 获取粉丝 id 集合
     *
     * @param dinerId
     * @return
     */
//    private List<Integer> findFollowers(Integer dinerId) {
//        String url = followServerName + "followers/" + dinerId;
//        ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class);
//        if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
//            throw new ParameterException(resultInfo.getCode(), resultInfo.getMessage());
//        }
//        List<Integer> followers = (List<Integer>) resultInfo.getData();
//
//        return followers;
//    }
    public List<Integer> findFollowers(Integer dinerId) {
        AssertUtil.isNotNull(dinerId, "请输入需要查看的人");
        // 获取 diner 的粉丝列表
        Set<Integer> followers = redisTemplate.opsForSet()
                .members(RedisKeyConstant.followers.getKey() + dinerId);
        AssertUtil.isTrue(followers.size() <= 0, "该用户没有粉丝");
        List<Integer> followerIds = new ArrayList<>(followers);
        return followerIds;
    }

    /**
     * 获取当前用户信息
     *
     * @param accessToken token
     * @return
     */
    private SignInDinerInfo loadSignInDinerInfo(String accessToken) {
        // 必须需要登录
        AssertUtil.mustLogin(accessToken);

        String url = oauthServerName + "user/me?access_token={accessToken}";

        ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);
        if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            throw new ParameterException(resultInfo.getMessage());
        }

        return BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(),
                new SignInDinerInfo(), false);

    }
}
